/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年7月23日
 */
package com.massyframework.beanskin.runtime.service.serialize;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Objects;

import com.massyframework.beanskin.Module;
import com.massyframework.beanskin.ModuleIdentifier;
import com.massyframework.beanskin.ModuleRepository;
import com.massyframework.beanskin.SimpleModuleIdentifier;
import com.massyframework.beanskin.service.serialize.SerializationException;
import com.massyframework.beanskin.service.serialize.Serializer;
import com.massyframework.beanskin.util.IOUtils;
import com.massyframework.beanskin.util.classloading.ClassLoaderUtils;
import com.massyframework.beanskin.util.classloading.ModuleClassLoader;
import com.massyframework.beanskin.util.version.Version;

/**
 * 基于Beanskin模块的序列化器
 * <p>
 * 适用于序列化和反序列化都是基于beaskin框架的场景
 * 
 * @author huangkh
 */
public class BeanSkinSerializer implements Serializer {
	
	private static ModuleIdentifier KERNEL = SimpleModuleIdentifier.createModuleIdentifierWithKernel();
	private static byte[] EMPTY = new byte[0];
	private ModuleRepository moduleRepository;

	/**
	 * 
	 */
	public BeanSkinSerializer(ModuleRepository moduleRepository) {
		this.moduleRepository = Objects.requireNonNull(moduleRepository, "\"moduleRepository\" cannot be null.");
	}

	@Override
	public byte[] serial(Object target) throws SerializationException {
		if (target == null)  return EMPTY;
		
		ClassLoader classLoader = target.getClass().getClassLoader();
		ModuleIdentifier identifier = KERNEL;
		if (classLoader instanceof ModuleClassLoader) {
			identifier = (ModuleClassLoader) classLoader;
		}
		
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		
		try {
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			
			oos.writeUTF(identifier.getGroupId());
			oos.writeUTF(identifier.getModuleId());
			oos.writeUTF(identifier.getVersion().toString());
			oos.flush();
			
			return bos.toByteArray();
		} catch (IOException e) {
			throw new SerializationException(e.getMessage(), e);
		}finally {
			IOUtils.close(oos);
			IOUtils.close(bos);
		}
	}

	@Override
	public Object deserial(byte[] bytes) throws SerializationException {
		if (bytes.length == 0) return null;
		
		ByteArrayInputStream bio = null;
		ObjectInputStream ois = null;
		
		try {
			bio = new ByteArrayInputStream(bytes);
			ois = new ObjectInputStream(bio);
			
			String groupId = ois.readUTF();
			String moduleId = ois.readUTF();
			String version = ois.readUTF();
			
			Module<?> module = this.moduleRepository
					.findModuleByIdentifier(groupId, moduleId, Version.parseVersion(version));
			if (module == null) {
				module = this.moduleRepository.findModuleById(0, Module.class);
			}
			
			ClassLoader classLoader = module.getModuleClassLoader();
			return this.doDeserial(ois, classLoader);
		}catch(Exception e) {
			throw new SerializationException(e.getMessage(), e);
		}finally {
			IOUtils.close(ois);
			IOUtils.close(bio);
		}
	}

	/**
	 * 反序列化
	 * @param ois
	 * @param classLoader
	 * @return
	 * @throws Exception
	 */
	protected Object doDeserial(ObjectInputStream ois, ClassLoader classLoader) throws Exception {
		ClassLoader contextLoader = 
				ClassLoaderUtils.setThreadContextClassLoader(classLoader);
		try {
			return ois.readObject();
		}catch(Exception e) {
			throw e;
		}finally {
			Thread.currentThread().setContextClassLoader(contextLoader);
		}
	}
}
